/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University. All rights reserved.
 * 
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license; 2) All redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the
 * disclaimer listed in this license in the documentation and/or other materials
 * provided with the distribution; 3) Any documentation included with all
 * redistributions must include the following acknowledgement:
 * 
 * "This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 * 
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 * 
 * 4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University. For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5) Products
 * derived from this software may not be called NaradaBrokering, nor may Indiana
 * University or Community Grids Lab or NaradaBrokering appear in their name,
 * without prior written permission of ARTI.
 * 
 * 
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of any
 * other entity. Indiana University disclaims any liability to any recipient for
 * claims brought by any other entity based on infringement of intellectual
 * property rights or otherwise.
 * 
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.event.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import cgl.narada.event.DistributionTraces;
import cgl.narada.event.EventHeaders;
import cgl.narada.event.EventID;
import cgl.narada.event.EventProperties;
import cgl.narada.event.NBEvent;
import cgl.narada.event.NBEventTypes;
import cgl.narada.event.TemplateProfileAndSynopsisTypes;

/**
 * This is the basic template event within the NaradaBrokering system. This
 * event includes among other things, information regarding a)The event's
 * headers b)The event's content synopsis c)The content payload
 * 
 * @author Shrideep Pallickara $Date$ $Revision$
 */

public class NBEventImpl implements NBEvent {

   private int eventType = 0;

   private boolean hasContentType;

   private String contentType;

   private boolean hasEventHeaders = false;

   private EventHeaders eventHeaders = null;

   private int headersLength = 0;

   private boolean hasEventProperties = false;

   private EventProperties eventProperties = null;

   private int propertiesLength = 0;

   private int contentSynopsisType;

   private Object contentSynopsis;

   private int payloadSize = 0;
   
   private int payloadOffset = 0;

   private byte[] payload = null;

   private boolean hasDistributionTraces = false;

   private DistributionTraces distributionTraces = null;
   
   private boolean distributionTracesModified = true;
   
   private byte[] marshalledBytes = null;
   
   private boolean debug = false;
   
   private String moduleName = "NBEventImpl: ";

   public NBEventImpl() {

   }

   public NBEventImpl(byte[] eventBytes) {
      ByteArrayInputStream baInputStream = new ByteArrayInputStream(eventBytes);
      DataInputStream din = new DataInputStream(baInputStream);
      
      try {

         din.readByte();
         /** cgl.narada.protocol.Protocol.DATA) */
         din.readByte();
         /** cgl.narada.protocol.Protocol.NB_EVENT */

         eventType = din.readByte();

         /** Deal with the content type */
         hasContentType = din.readBoolean();
         if (hasContentType) {
            int _contentTypeLength = din.readShort();
            byte[] _contentTypeBytes = new byte[_contentTypeLength];
            din.readFully(_contentTypeBytes);
            contentType = new String(_contentTypeBytes);
         }

         /** Deal with the event headers */
         hasEventHeaders = din.readBoolean();
         if (hasEventHeaders) {
            headersLength = din.readInt();
            byte[] _headerBytes = new byte[headersLength];
            din.readFully(_headerBytes);
            eventHeaders = new EventHeadersImpl(_headerBytes);
         }

         boolean hasPropertiesToRead = din.readBoolean();
         if (hasPropertiesToRead) {
            hasEventProperties = true;
            propertiesLength = din.readInt();
            byte[] _propertyBytes = new byte[propertiesLength];
            din.readFully(_propertyBytes);
            eventProperties = new EventPropertiesImpl(_propertyBytes);
         }

         /** Deal with the content synopis */
         contentSynopsisType = din.readByte();
         unMarshallContentSynopsis(din);

         /** Deal with the payload */
         payloadOffset = din.readInt();
         payloadSize = din.readInt();
         if (payloadSize != 0) {
            //payload = new byte[payloadSize];
            //din.readFully(payload);
            
            int skippedBytes = din.skipBytes(payloadSize);
            if (skippedBytes != payloadSize) {
               System.out.println(moduleName + "Unable to SKIP (" + payloadSize +
                     ") Bytes");
            }
         }

         /** Deal with the distribution traces */
         hasDistributionTraces = din.readBoolean();
         if (hasDistributionTraces) {
            int _distributionTracesLength = din.readShort();
            byte[] _distributionBytes = new byte[_distributionTracesLength];
            din.readFully(_distributionBytes);

            distributionTraces = new DistributionTracesImpl(_distributionBytes);

         } else {
            distributionTraces = new DistributionTracesImpl();
         }

         distributionTracesModified = false;
         marshalledBytes = eventBytes;
         baInputStream = null;
         din = null;
      } catch (IOException ioe) {
         System.out
                   .println(moduleName + "Error while un-marshalling NBEvent");
      }
      /** end try-catch block */

   }

   /** A utlity method which unmarshalls the content synsopsis */
   private void unMarshallContentSynopsis(DataInputStream din)
         throws IOException {
      byte[] _synopsisBytes;
      if (contentSynopsisType == TemplateProfileAndSynopsisTypes.STRING
          || contentSynopsisType == TemplateProfileAndSynopsisTypes.TAG_VALUE_PAIRS
          || contentSynopsisType == TemplateProfileAndSynopsisTypes.REGULAR_EXPRESSIONS) {
         _synopsisBytes = new byte[din.readInt()];
         din.readFully(_synopsisBytes);
         contentSynopsis = new String(_synopsisBytes);
         return;
      }

      if (contentSynopsisType == TemplateProfileAndSynopsisTypes.INTEGER) {
         int _synopsisInt = din.readInt();
         contentSynopsis = new Integer(_synopsisInt);
         return;
      }

      /** Retrieve serilaized representation of the content synopsis object */
      _synopsisBytes = new byte[din.readInt()];
      din.readFully(_synopsisBytes);

      /**
       * Now that we have the serialized representation of the content synopis
       * we need to construct it
       */
      ByteArrayInputStream baInputStream = new ByteArrayInputStream(
                                                                    _synopsisBytes);
      ObjectInputStream din2 = new ObjectInputStream(baInputStream);
      try {
         contentSynopsis = din2.readObject();
      } catch (ClassNotFoundException cnfe) {
         System.out.println(moduleName + cnfe);
      }
   }

   /** Sets the event type information */
   protected void setEventType(int eventType) {
      this.eventType = eventType;
   }

   /** Set the content synopis information */
   protected void setContentSynopsisInfo(int contentSynopsisType,
                                         Object contentSynopsis) {
      this.contentSynopsisType = contentSynopsisType;
      this.contentSynopsis = contentSynopsis;
   }

   /** Set the event headers */
   protected void setEventHeaders(EventHeaders eventHeaders) {
      if (eventHeaders == null) {
         System.out.println(moduleName + "Specified event headers is NULL");
         return;
      }
      this.eventHeaders = eventHeaders;
      hasEventHeaders = true;
   }

   /** Set the payload */
   protected void setContentPayload(byte[] payload) {
      if (payload == null) {
         System.out.println(moduleName + "Specified payload is NULL");
         return;
      }
      this.payload = payload;
      payloadSize = this.payload.length;

   }

   /** Set the dissemination traces */
   protected void setDistributionTraces(DistributionTraces distributionTraces) {
      if (distributionTraces == null) {
         hasDistributionTraces = false;
      } else {
         hasDistributionTraces = true;
      }

      this.distributionTraces = distributionTraces;
      distributionTracesModified = true;
   }

   /** ****************************************************************** */
   /** Implementation of NBEvent Interface methods begins */
   /** ****************************************************************** */
   /** Indicates the type of the event */
   public int getEventType() {
      return eventType;
   }

   /** Indicates the type of the content */
   public String getContentType() {
      return contentType;
   }

   /** Indicates if this event has any event headers */
   public boolean hasEventHeaders() {
      return hasEventHeaders;
   }

   /** Header information pertaining to the event */
   public EventHeaders getEventHeaders() {
      return eventHeaders;
   }

   /** Indicates if this event has any user-defined properties */
   public boolean hasEventProperties() {
      return hasEventProperties;
   }

   /** EventProperties associated with the event */
   public EventProperties getEventProperties() {
      if (eventProperties == null) {
         eventProperties = new EventPropertiesImpl();
         hasEventProperties = true;
      }

      return eventProperties;
   }

   /** Returns the type of the content synopsis */
   public int getContentSynopsisType() {
      return contentSynopsisType;
   }

   /** Retrieves the synopis associated with the content */
   public Object getContentSynopsis() {
      return contentSynopsis;
   }

   /** Get the payload size */
   public int getContentPayloadSize() {
      return payloadSize;
   }

   /** Get the content Payload */
   public byte[] getContentPayload() {
      if (payload == null) {
         if (payloadOffset!=0 && marshalledBytes != null) {
            payload = new byte[payloadSize];
            System.arraycopy(marshalledBytes, payloadOffset, payload, 0, payloadSize);
         }
      }

      return payload;
   }

   /** Indicates if the event has distribution traces */
   public boolean hasDistributionTraces() {
      return hasDistributionTraces;
   }

   /** Gets the distribution Traces associated with the event */
   public DistributionTraces getDistributionTraces() {
      return distributionTraces;
   }

   /** Generates a byte stream representation of the event */
   public byte[] getBytes() {
      ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream(
                                                                       payloadSize + 200);

      DataOutputStream dout = new DataOutputStream(baOutputStream);
      
      if (!eventToBeMarshalled()) {
         if (distributionTracesModified) {
            
            byte[] _distributionTraceBytes = distributionTraces.getBytes();
            int _traceLength = _distributionTraceBytes.length;
            int _offset = marshalledBytes.length - _traceLength ;
            
            if (debug) {
               System.out.println(moduleName + "Copying at offset " + _offset +
                     " eventBytesLength=" + marshalledBytes.length +
                     " Trace size =" + _traceLength);
            }
            marshalledBytes[_offset-3] = 1; //hasDistributionTraces = true;
            System.arraycopy(_distributionTraceBytes, 0, marshalledBytes,
                  _offset, _traceLength);
         }
         return marshalledBytes;
      } else {
         if (debug) { 
            System.out.println(moduleName + "Event needs to be marshalled again!");
         }
      }

      int offset = 0;
      
      try {
         dout.writeByte(cgl.narada.protocol.Protocol.DATA);
         offset++;
         dout.writeByte(cgl.narada.protocol.Protocol.NB_EVENT);
         offset++;
         dout.writeByte((byte) eventType);
         offset++;
         
         /** Deal with the content type */
         dout.writeBoolean(hasContentType);
         offset++;
         
         if (hasContentType) {
            dout.writeShort((short) contentType.length());
            offset+=2;
            
            byte[] contentTypeBytes = contentType.getBytes();
            dout.write(contentTypeBytes);
            offset+=contentTypeBytes.length;
         }

         /** Deal with the event headers */
         dout.writeBoolean(hasEventHeaders);
         offset++;
         
         if (hasEventHeaders) {
            byte[] headerBytes = eventHeaders.getBytes();
            dout.writeInt(headerBytes.length);
            offset+=4;
            dout.write(headerBytes);
            offset+=headerBytes.length;
         }

         /** Deal with event properties */
         boolean willWriteProperties = false;
         if (hasEventProperties) {
            if (eventProperties.hasProperties()) {
               willWriteProperties = true;
            }
         }

         dout.writeBoolean(willWriteProperties);
         offset++;
         
         if (willWriteProperties) {
            byte[] propertyBytes = eventProperties.getBytes();
            dout.writeInt(propertyBytes.length);
            offset+=4;
            dout.write(propertyBytes);
            offset+=propertyBytes.length;
         }

         /** Deal with the content synopis */
         dout.writeByte((byte) contentSynopsisType);
         offset++;
         offset+=marshallContentSynopsis(dout);

         /** Deal with the payload */
         int _payloadOffset = offset + 8;
         dout.writeInt(_payloadOffset);
         dout.writeInt(payloadSize);
         if (payload != null && payloadSize != 0)
            dout.write(payload);

         /** Deal with the distribution traces */
         dout.writeBoolean(hasDistributionTraces);
         if (hasDistributionTraces) {
            byte[] _distributionBytes = distributionTraces.getBytes();
            dout.writeShort(_distributionBytes.length);
            dout.write(_distributionBytes);
         } else {
            DistributionTraces placeHolderTraces = new DistributionTracesImpl();
            byte[] _distributionBytes = placeHolderTraces.getBytes();
            dout.writeShort(_distributionBytes.length);
            dout.write(_distributionBytes);
            
         }

         dout.flush();
         marshalledBytes = baOutputStream.toByteArray();
         baOutputStream = null;
         dout = null;
      } catch (IOException ioe) {
         System.out
                   .println(moduleName + "Error while marshalling NBEvent");
      }
      /** end try-catch block */

      return marshalledBytes;
   }
   

   

   /** ****************************************************************** */
   /** Implementation of NBEvent Interface methods complete */
   /** ****************************************************************** */

   /** A utlity method which marshalls the content synsopsis */
   private int marshallContentSynopsis(DataOutputStream dout)
         throws IOException {
      int offset =0;
      
      if (contentSynopsisType == TemplateProfileAndSynopsisTypes.STRING
          || contentSynopsisType == TemplateProfileAndSynopsisTypes.TAG_VALUE_PAIRS
          || contentSynopsisType == TemplateProfileAndSynopsisTypes.REGULAR_EXPRESSIONS) {
         String _contentSynopsis = (String) contentSynopsis;
         dout.writeInt(_contentSynopsis.length());
         offset+=4;
         byte[] _contentSynopsisBytes = _contentSynopsis.getBytes();
         dout.write(_contentSynopsisBytes);
         offset+=_contentSynopsisBytes.length;
         return offset;
      }

      if (contentSynopsisType == TemplateProfileAndSynopsisTypes.INTEGER) {
         dout.writeInt(((Integer) contentSynopsis).intValue());
         offset+=4;
         return offset;
      }

      /** Get a serilaized representation of the content synopsis object */
      ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
      ObjectOutputStream dout2 = new ObjectOutputStream(baOutputStream);
      dout2.writeObject(contentSynopsis);
      byte[] contentSynopsisBytes = baOutputStream.toByteArray();

      /**
       * Now that we have the serialized representation of the content synopis
       * we need to write it to dout
       */
      dout.writeInt(contentSynopsisBytes.length);
      offset+=4;
      dout.write(contentSynopsisBytes);
      offset+=contentSynopsisBytes.length;
      return offset;

   }

   
   private boolean eventToBeMarshalled() {
      boolean needsToBeMarshalledAgain = false;
      
      if (marshalledBytes == null) {
         return true;
      }
      
      if (hasEventHeaders) {
         if (eventHeaders.isModified()) {
            needsToBeMarshalledAgain = true;
            if (debug) {
               System.out.println(moduleName + "Event headers modified");
            }
         }
      }
      
      if (hasEventProperties) {
         if (eventProperties.isModified()) {
            needsToBeMarshalledAgain = true;
            if (debug) {
               System.out.println(moduleName + "Event properties modified");
            }
         }
      }
      
      return needsToBeMarshalledAgain;
   }
   
   /** A utility method to test the NBEvent implementation */
   public static void main(String[] args) {
      EventHeadersImpl headers1 = new EventHeadersImpl();
      headers1.setPersistent();
      headers1.setFragmentationInformation("fileToFragment.big", (long) 2000,
            10, 1000, System.currentTimeMillis());
      headers1.setEventId(new EventID(123, (long) 345, 324));

      int eventType = NBEventTypes.NORMAL_EVENT;

      String contentType = "This is a audio-video conferencing packet";
      int contentSynopsisType = TemplateProfileAndSynopsisTypes.STRING;
      String contentSynopsis = "/Multimedia/Audio/Format62/wav";

      byte[] payload = new String("Here's the payload").getBytes();

      byte[] toReach = new String("123456789").getBytes();
      byte[] traversedSoFar = new String("987654321").getBytes();
      DistributionTracesImpl traces = new DistributionTracesImpl();
      traces.setDestinationsToReach(toReach);
      traces.setDestinationsTraversedSoFar(traversedSoFar);

      NBEventImpl event1 = new NBEventImpl();
      event1.setEventHeaders(headers1);
      event1.setContentSynopsisInfo(contentSynopsisType, contentSynopsis);
      event1.setContentPayload(payload);
      event1.setDistributionTraces(traces);

      /** Print distribution traces */
      NBEventImpl event2 = new NBEventImpl(event1.getBytes());

      /** Print content synopsis */
      System.out.println("Synopsis type = " + event2.getContentSynopsisType()
                         + ", Synopsis = " + event2.getContentSynopsis());

      System.out.println("Content Payload = "
                         + new String(event2.getContentPayload()));

      DistributionTraces traces2 = event2.getDistributionTraces();
      toReach = traces2.getDestinationsToReach();
      traversedSoFar = traces2.getDestinationsTraversedSoFar();

      if (toReach != null)
         System.out.println("ToReach " + new String(toReach));
      if (traversedSoFar != null)
         System.out.println("TraversedSoFar " + new String(traversedSoFar));

   }

}
